/*
 *
 *  * Copyright (c) iwindplus Technologies Co., Ltd.2024-2030, All rights reserved.
 *
 *
 */

package com.iwindplus.base.util;

import cn.hutool.core.util.PrimitiveArrayUtil;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.iwindplus.base.domain.constant.CommonConstant;
import com.iwindplus.base.domain.enums.BizCodeEnum;
import com.iwindplus.base.domain.exception.BizException;
import com.iwindplus.base.util.domain.constant.UtilConstant.GenericObjectPoolConstant;
import com.iwindplus.base.util.handler.PooledKryoFactory;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

/**
 * Kryo工具类.
 *
 * @author zengdegui
 * @since 2018/9/1
 */
@Slf4j
public class KryoUtil {

    private KryoUtil() {
        throw new IllegalStateException(CommonConstant.UTILITY_CLASS);
    }

    private static final AtomicReference<GenericObjectPool<Kryo>> POOL = new AtomicReference<>();

    private static final AtomicBoolean BUILD_FLAG = new AtomicBoolean(false);

    /**
     * 获取GenericObjectPool.
     *
     * @return GenericObjectPool<Kryo>
     */
    private static GenericObjectPool<Kryo> getPool() {
        return POOL.updateAndGet(pool -> {
            if (pool != null) {
                return pool;
            }

            log.warn("POOL is null, build default pool urgently");
            return buildPool();
        });
    }


    /**
     * 设置 GenericObjectPool.
     *
     * @param pool 池配置
     */
    public static void setPool(GenericObjectPool<Kryo> pool) {
        if (BUILD_FLAG.compareAndSet(false, true)) {
            POOL.set(pool);
            log.info("Kryo pool build success");
            return;
        }

        log.warn("Kryo pool already built, later config ignored");
    }

    /**
     * 序列化为字节数组.
     *
     * @param obj 任意对象
     * @param <T> 对象的类型
     * @return byte[]
     */
    public static <T> byte[] toJsonBytes(T obj) {
        if (Objects.isNull(obj)) {
            return new byte[0];
        }

        Kryo kryo = borrowBuffer();
        try (Output output = new Output(GenericObjectPoolConstant.DEFAULT_BUFFER_SIZE, -1)) {
            kryo.writeClassAndObject(output, obj);
            return output.toBytes();
        } catch (Exception ex) {
            log.error("Kryo serialize error", ex);
            throw new BizException(BizCodeEnum.SERIALIZE_ERROR);
        } finally {
            KryoUtil.release(kryo);
        }
    }

    /**
     * 反序列化为对象.
     *
     * @param bytes 字节数组
     * @param clazz 目标类
     * @param <T>   泛型
     * @return T
     */
    public static <T> T parseBytes(byte[] bytes, Class<T> clazz) {
        if (PrimitiveArrayUtil.isEmpty(bytes)) {
            return null;
        }

        Kryo kryo = borrowBuffer();
        try (Input input = new Input(bytes)) {
            Object obj = kryo.readClassAndObject(input);
            return clazz.cast(obj);
        } catch (Exception ex) {
            log.error("Kryo deserialize error", ex);
            throw new BizException(BizCodeEnum.DESERIALIZE_ERROR);
        } finally {
            KryoUtil.release(kryo);
        }
    }

    public static Kryo borrowBuffer() {
        try {
            return getPool().borrowObject();
        } catch (Exception ex) {
            log.error("getBuffer error={}", ex.getMessage(), ex);
            throw new BizException(BizCodeEnum.GET_BUFFER_ERROR);
        }
    }

    private static GenericObjectPool<Kryo> buildPool() {
        GenericObjectPoolConfig<Kryo> cfg = new GenericObjectPoolConfig<>();
        cfg.setMaxTotal(GenericObjectPoolConstant.DEFAULT_MAX_TOTAL);
        cfg.setMaxIdle(GenericObjectPoolConstant.DEFAULT_MAX_IDLE);
        cfg.setMinIdle(GenericObjectPoolConstant.DEFAULT_MIN_IDLE);
        cfg.setMaxWait(GenericObjectPoolConstant.DEFAULT_MAX_WAIT);
        cfg.setTestOnBorrow(false);
        cfg.setTestOnReturn(false);
        cfg.setBlockWhenExhausted(true);
        return new GenericObjectPool<>(new PooledKryoFactory(), cfg);
    }

    /**
     * 归还到池中.
     *
     * @param kryo 之前借出的缓冲区（允许 null）
     */
    public static void release(Kryo kryo) {
        if (kryo == null) {
            return;
        }

        try {
            getPool().returnObject(kryo);
        } catch (Exception e) {
            log.error("return Kryo error", e);
        }
    }

}